Load Libraries


packages <- c(
  'ggplot2','tidyverse','plotly','leaflet',
  'shiny','shinyWidgets','shinydashboard',
  'xts','forecast','TTR',
  'DT','lubridate','RColorBrewer','scales','stopwords',
  'tidytext','stringr','wordcloud','wordcloud2','scales','dplyr','rfm',
  'SnowballC','textmineR','topicmodels','textclean','tm'
)
for (package in packages) { 
  if (!require(package, character.only = T, quietly = T)) {
    install.packages(package)
    library(package, character.only = T)
  }
}

Load data

crm <- read_csv("CRM_interacions_table.csv")
gift <- read_csv("gift_transactions_table.csv")
video <- read_csv("video_email_data_table.csv")
constituent <- read_csv("constituent_profiles_table.csv")

Part 1: The Untapped Potential: Understanding Our Donor Landscape

CRM Data Overview

# CRM Interaction Type
g <- crm %>%
        group_by(CRM_INTERACTION_TYPE) %>%
        summarise(Total = n()) %>%
        select(CRM_INTERACTION_TYPE, Total) %>%
        ggplot(aes(x = reorder(CRM_INTERACTION_TYPE,Total) ,y = Total))  +
        geom_bar(stat = "identity",width = 0.5, fill='black')  +
        scale_y_continuous(labels = scales::comma) +
        labs(x ="CRM Interaction Type", y = "Count") + coord_flip() +
        theme(legend.text = element_text(size = 12),
              legend.title = element_text(size = 12),
              axis.title = element_text(size = 14),
              axis.text = element_text(size = 12))
      
ggplotly(g)

CRM Interaction Over Time

crm <- crm %>%
        mutate(Year = lubridate::year(CRM_INTERACTION_DATE),
               Quarter = lubridate::quarter(CRM_INTERACTION_DATE),
               Month = lubridate::month(CRM_INTERACTION_DATE, label = TRUE),
               DOW = lubridate::wday(CRM_INTERACTION_DATE, label=TRUE))

CRM Interaction by Year

crm_year <- crm %>%
group_by(Year, CRM_INTERACTION_TYPE) %>%
        summarise(Total = n()) %>%
        select(Year,CRM_INTERACTION_TYPE, Total)

   g <- ggplot(crm_year, aes(as.factor(Year), Total, group=CRM_INTERACTION_TYPE, colour = CRM_INTERACTION_TYPE)) + 
      geom_line( linewidth=1) + theme_minimal() +
      labs(x = "Year", y = "Total", color="CRM Interaction Type") + 
      scale_y_continuous(labels = comma) +
      theme(legend.text = element_text(size = 10),
            legend.title = element_text(size = 10),
            axis.title = element_text(size = 10),
            axis.text = element_text(size = 10),
            axis.text.x = element_text(angle = 0, hjust = 1))
ggplotly(g)

CRM Interaction by Quarter

crm %>%
group_by(Quarter, CRM_INTERACTION_TYPE) %>%
        summarise(Total = n()) %>%
        select(Quarter,CRM_INTERACTION_TYPE, Total) %>% 
      ggplot(aes(as.factor(Quarter), Total, group=CRM_INTERACTION_TYPE, colour = CRM_INTERACTION_TYPE)) + 
      geom_line( linewidth=1) + theme_minimal() +
      labs(x = "Quarter", y = "Total", color="CRM Interaction Type") + 
      scale_y_continuous(labels = comma) +
      theme(legend.text = element_text(size = 10),
            legend.title = element_text(size = 10),
            axis.title = element_text(size = 10),
            axis.text = element_text(size = 10),
            axis.text.x = element_text(angle = 0, hjust = 1))

CRM Interaction by Month

crm %>%
group_by(Month, CRM_INTERACTION_TYPE) %>%
        summarise(Total = n()) %>%
        select(Month,CRM_INTERACTION_TYPE, Total) %>% 
      ggplot(aes(as.factor(Month), Total, group=CRM_INTERACTION_TYPE, colour = CRM_INTERACTION_TYPE)) + 
      geom_line( linewidth=1) + theme_minimal() +
      labs(x = "Quarter", y = "Total", color="CRM Interaction Type") + 
      scale_y_continuous(labels = comma) +
      theme(legend.text = element_text(size = 10),
            legend.title = element_text(size = 10),
            axis.title = element_text(size = 10),
            axis.text = element_text(size = 10),
            axis.text.x = element_text(angle = 0, hjust = 1))

CRM Interaction by Day of Week

crm %>%
group_by(DOW, CRM_INTERACTION_TYPE) %>%
        summarise(Total = n()) %>%
        select(DOW,CRM_INTERACTION_TYPE, Total) %>% 
      ggplot(aes(as.factor(DOW), Total, group=CRM_INTERACTION_TYPE, colour = CRM_INTERACTION_TYPE)) + 
      geom_line( linewidth=1) + theme_minimal() +
      labs(x = "Day of Week", y = "Total", color="CRM Interaction Type") + 
      scale_y_continuous(labels = comma) +
      theme(legend.text = element_text(size = 10),
            legend.title = element_text(size = 10),
            axis.title = element_text(size = 10),
            axis.text = element_text(size = 10),
            axis.text.x = element_text(angle = 0, hjust = 1))

Gift Overview

Gifts by CRM Interaction Type

left_join(gift,crm,by='CONSTITUENT_ID') %>%
  group_by(CRM_INTERACTION_TYPE) %>%
  summarise(Total = sum(AMOUNT)) %>%
  select(CRM_INTERACTION_TYPE,Total) %>%
  ggplot(aes(x = reorder(CRM_INTERACTION_TYPE,Total) ,y = Total))  +
        geom_bar(stat = "identity",width = 0.5, fill='black')  +
        scale_y_continuous(labels = scales::comma) +
        labs(x ="CRM Interaction Type", y = "Donations") + coord_flip() +
        theme(legend.text = element_text(size = 12),
              legend.title = element_text(size = 12),
              axis.title = element_text(size = 14),
              axis.text = element_text(size = 12))

Gifts overtime

gift <- gift %>%
        mutate(Year = lubridate::year(GIFT_DATE),
               Quarter = lubridate::quarter(GIFT_DATE),
               Month = lubridate::month(GIFT_DATE, label = TRUE),
               DOW = lubridate::wday(GIFT_DATE, label=TRUE))

Gift by Year

g <- gift %>%
group_by(Year) %>%
        summarise(Total = sum(AMOUNT)) %>%
        select(Year, Total) %>% 
        na.omit() %>%
      ggplot(aes(Year, Total)) + 
      geom_bar(stat = "identity",width = 0.5, fill='black') + theme_minimal() +
      labs(x = "Year", y = "Total") + 
      scale_y_continuous(labels = comma) +
      theme(legend.text = element_text(size = 10),
            legend.title = element_text(size = 10),
            axis.title = element_text(size = 10),
            axis.text = element_text(size = 10),
            axis.text.x = element_text(angle = 90, hjust = 1))
ggplotly(g)

Gift by Quarter

g <- gift %>%
group_by(Quarter) %>%
        summarise(Total = sum(AMOUNT)) %>%
        select(Quarter, Total) %>% 
        na.omit() %>%
      ggplot(aes(Quarter, Total)) + 
      geom_bar(stat = "identity",width = 0.5, fill='black') + theme_minimal() +
      labs(x = "Quarter", y = "Total") + 
      scale_y_continuous(labels = comma) +
      theme(legend.text = element_text(size = 10),
            legend.title = element_text(size = 10),
            axis.title = element_text(size = 10),
            axis.text = element_text(size = 10),
            axis.text.x = element_text(angle = 0, hjust = 1))
ggplotly(g)

Gift by Month

g <- gift %>%
group_by(Month) %>%
        summarise(Total = sum(AMOUNT)) %>%
        select(Month, Total) %>% 
        na.omit() %>%
      ggplot(aes(Month, Total)) + 
      geom_bar(stat = "identity",width = 0.5, fill='black') + theme_minimal() +
      labs(x = "Month", y = "Total") + 
      scale_y_continuous(labels = comma) +
      theme(legend.text = element_text(size = 10),
            legend.title = element_text(size = 10),
            axis.title = element_text(size = 10),
            axis.text = element_text(size = 10),
            axis.text.x = element_text(angle = 0, hjust = 1))
ggplotly(g)

Gift by Day of Week

g <- gift %>%
group_by(DOW) %>%
        summarise(Total = sum(AMOUNT)) %>%
        select(DOW, Total) %>% 
        na.omit() %>%
      ggplot(aes(DOW, Total)) + 
      geom_bar(stat = "identity",width = 0.5, fill='black') + theme_minimal() +
      labs(x = "Day of Week", y = "Total") + 
      scale_y_continuous(labels = comma) +
      theme(legend.text = element_text(size = 10),
            legend.title = element_text(size = 10),
            axis.title = element_text(size = 10),
            axis.text = element_text(size = 10),
            axis.text.x = element_text(angle = 0, hjust = 1))
ggplotly(g)

Video Overview

Video Views over time

video <- video %>%
        mutate(Year = lubridate::year(SENT_DATE),
               Quarter = lubridate::quarter(SENT_DATE),
               Month = lubridate::month(SENT_DATE, label = TRUE),
               DOW = lubridate::wday(SENT_DATE, label=TRUE))

Video views by year

g <- video %>%
group_by(Year) %>%
        summarise(Total = sum(VIDEO_VIEWS)) %>%
        select(Year, Total) %>% 
        na.omit() %>%
      ggplot(aes(as.factor(Year), Total)) + 
      geom_bar(stat = "identity",width = 0.5, fill='black') + theme_minimal() +
      labs(x = "Year", y = "Total") + 
      scale_y_continuous(labels = comma) +
      theme(legend.text = element_text(size = 10),
            legend.title = element_text(size = 10),
            axis.title = element_text(size = 10),
            axis.text = element_text(size = 10),
            axis.text.x = element_text(angle = 0, hjust = 1))
ggplotly(g)

Video Views by Quarter

g <- video %>%
group_by(Quarter) %>%
        summarise(Total = sum(VIDEO_VIEWS)) %>%
        select(Quarter, Total) %>% 
        na.omit() %>%
      ggplot(aes(Quarter, Total)) + 
      geom_bar(stat = "identity",width = 0.5, fill='black') + theme_minimal() +
      labs(x = "Quarter", y = "Total") + 
      scale_y_continuous(labels = comma) +
      theme(legend.text = element_text(size = 10),
            legend.title = element_text(size = 10),
            axis.title = element_text(size = 10),
            axis.text = element_text(size = 10),
            axis.text.x = element_text(angle = 0, hjust = 1))
ggplotly(g)

Video Views by Month

g <- video %>%
group_by(Month) %>%
        summarise(Total = sum(VIDEO_VIEWS)) %>%
        select(Month, Total) %>% 
        na.omit() %>%
      ggplot(aes(Month, Total)) + 
      geom_bar(stat = "identity",width = 0.5, fill='black') + theme_minimal() +
      labs(x = "Month", y = "Total") + 
      scale_y_continuous(labels = comma) +
      theme(legend.text = element_text(size = 10),
            legend.title = element_text(size = 10),
            axis.title = element_text(size = 10),
            axis.text = element_text(size = 10),
            axis.text.x = element_text(angle = 0, hjust = 1))
ggplotly(g)

Video Views by Day of Week

g <- video %>%
group_by(DOW) %>%
        summarise(Total = sum(VIDEO_VIEWS)) %>%
        select(DOW, Total) %>% 
        na.omit() %>%
      ggplot(aes(DOW, Total)) + 
      geom_bar(stat = "identity",width = 0.5, fill='black') + theme_minimal() +
      labs(x = "Day of the week", y = "Total") + 
      scale_y_continuous(labels = comma) +
      theme(legend.text = element_text(size = 10),
            legend.title = element_text(size = 10),
            axis.title = element_text(size = 10),
            axis.text = element_text(size = 10),
            axis.text.x = element_text(angle = 0, hjust = 1))
ggplotly(g)

Part 2: A Tale of Portfolios/Customer Segmentation

Donor RFM Analysis


rfm_df <- gift %>%
  select(CONSTITUENT_ID,GIFT_DATE,AMOUNT) %>%
  na.omit()

names(rfm_df)[names(rfm_df) == 'CONSTITUENT_ID'] <- 'customer_id'

#rfm model setup
analysis_date <- lubridate::as_date(today(), tz = "UTC")

report <- rfm_table_order(rfm_df, customer_id,GIFT_DATE,AMOUNT, analysis_date)
#segment
segment_titles <- c("First Grade", "Loyal", "Likely to be Loyal",
                    "New Ones", "Could be Promising", "Require Assistance", "Getting Less Frequent",
                    "Almost Out", "Can't Lose Them", "Don’t Show Up at All")
#numerical thresholds
 r_low <- c(4, 2, 3, 4, 3, 2, 2, 1, 1, 1)
 r_high <- c(5, 5, 5, 5, 4, 3, 3, 2, 1, 2)
 f_low <- c(4, 3, 1, 1, 1, 2, 1, 2, 4, 1)
 f_high <- c(5, 5, 3, 1, 1, 3, 2, 5, 5, 2)
 m_low <- c(4, 3, 1, 1, 1, 2, 1, 2, 4, 1)
 m_high  <- c(5, 5, 3, 1, 1, 3, 2, 5, 5, 2)

divisions<-rfm_segment(report, segment_titles, r_low, r_high, f_low, f_high, m_low, m_high)

division_count <- divisions %>% count(segment) %>% arrange(desc(n)) %>% rename(Segment = segment, Count = n)

RFM heatmap

rfm_plot_heatmap(report)

rfm_plot_bar_chart(report)

rfm_plot_histogram(report)

Donor Lifetime Value

# Calculate average donation per customer
avg_donation_per_customer <- gift %>%
  group_by(CONSTITUENT_ID) %>%
  summarize(avg_revenue = mean(AMOUNT))

# Calculate average donor lifespan (simplified, using the number of months)
avg_donor_lifespan <- gift %>%
  group_by(CONSTITUENT_ID) %>% 
  summarize(avg_lifespan = as.numeric(difftime(max(GIFT_DATE), min(GIFT_DATE), units = "days"))) %>%
  na.omit()

# Calculate CLV
clv_df <- inner_join(avg_donation_per_customer,avg_donor_lifespan,by='CONSTITUENT_ID') %>%
group_by(CONSTITUENT_ID) %>%
  mutate(CLV_calc = avg_revenue * avg_lifespan) %>%
  select(CONSTITUENT_ID,CLV_calc)
#clv <- avg_revenue_per_customer$avg_revenue * avg_lifespan$avg_lifespan

#print(clv)
## CLV version 2
# 2. Calculate key metrics for each customer
customer_data <- transactions %>%
  group_by(customer_id) %>%
  summarise(
    frequency = n() - 1, # Number of repeat purchases
    recency = as.numeric(difftime(max(date), min(date), units = "days")),
    total_revenue = sum(revenue),
    monetary = total_revenue / n()
  )

# 3. Calculate components for historical CLV
# Average Purchase Value (APV)
apv <- sum(transactions$revenue) / nrow(transactions)

# Average Purchase Frequency Rate (APFR)
total_purchases <- nrow(transactions)
total_customers <- length(unique(transactions$customer_id))
apfr <- total_purchases / total_customers

# Customer Value (CV)
cv <- apv * apfr

# 4. Calculate a simple historical CLV
# We need to assume a customer lifetime. Let's assume an average customer lifetime of 365 days.
# And a profit margin of 20%
profit_margin <- 0.20
average_customer_lifetime <- 365 # in days

# Average customer lifespan in relation to the observation period
# Here we calculate average lifespan based on recency
average_lifespan <- mean(customer_data$recency)

# Simple CLV
clv_simple <- cv * average_lifespan * profit_margin

Part 3: The Path Forward: Activating Our Strategy

LS0tCnRpdGxlOiAiQXByYSBEYXRhIFNjaWVuY2UgQ2hhbGxlbmdlIDIwMjUiCm91dHB1dDogaHRtbF9ub3RlYm9vawotLS0KCiMgTG9hZCBMaWJyYXJpZXMKYGBge3J9CgpwYWNrYWdlcyA8LSBjKAogICdnZ3Bsb3QyJywndGlkeXZlcnNlJywncGxvdGx5JywnbGVhZmxldCcsCiAgJ3NoaW55Jywnc2hpbnlXaWRnZXRzJywnc2hpbnlkYXNoYm9hcmQnLAogICd4dHMnLCdmb3JlY2FzdCcsJ1RUUicsCiAgJ0RUJywnbHVicmlkYXRlJywnUkNvbG9yQnJld2VyJywnc2NhbGVzJywnc3RvcHdvcmRzJywKICAndGlkeXRleHQnLCdzdHJpbmdyJywnd29yZGNsb3VkJywnd29yZGNsb3VkMicsJ3NjYWxlcycsJ2RwbHlyJywncmZtJywKICAnU25vd2JhbGxDJywndGV4dG1pbmVSJywndG9waWNtb2RlbHMnLCd0ZXh0Y2xlYW4nLCd0bScKKQpmb3IgKHBhY2thZ2UgaW4gcGFja2FnZXMpIHsgCiAgaWYgKCFyZXF1aXJlKHBhY2thZ2UsIGNoYXJhY3Rlci5vbmx5ID0gVCwgcXVpZXRseSA9IFQpKSB7CiAgICBpbnN0YWxsLnBhY2thZ2VzKHBhY2thZ2UpCiAgICBsaWJyYXJ5KHBhY2thZ2UsIGNoYXJhY3Rlci5vbmx5ID0gVCkKICB9Cn0KYGBgCgojIExvYWQgZGF0YQpgYGB7cn0KY3JtIDwtIHJlYWRfY3N2KCJDUk1faW50ZXJhY2lvbnNfdGFibGUuY3N2IikKZ2lmdCA8LSByZWFkX2NzdigiZ2lmdF90cmFuc2FjdGlvbnNfdGFibGUuY3N2IikKdmlkZW8gPC0gcmVhZF9jc3YoInZpZGVvX2VtYWlsX2RhdGFfdGFibGUuY3N2IikKY29uc3RpdHVlbnQgPC0gcmVhZF9jc3YoImNvbnN0aXR1ZW50X3Byb2ZpbGVzX3RhYmxlLmNzdiIpCmBgYAoKIyBQYXJ0IDE6IFRoZSBVbnRhcHBlZCBQb3RlbnRpYWw6IFVuZGVyc3RhbmRpbmcgT3VyIERvbm9yIExhbmRzY2FwZQoKIyMgQ1JNIERhdGEgT3ZlcnZpZXcKYGBge3J9CiMgQ1JNIEludGVyYWN0aW9uIFR5cGUKZyA8LSBjcm0gJT4lCiAgICAgICAgZ3JvdXBfYnkoQ1JNX0lOVEVSQUNUSU9OX1RZUEUpICU+JQogICAgICAgIHN1bW1hcmlzZShUb3RhbCA9IG4oKSkgJT4lCiAgICAgICAgc2VsZWN0KENSTV9JTlRFUkFDVElPTl9UWVBFLCBUb3RhbCkgJT4lCiAgICAgICAgZ2dwbG90KGFlcyh4ID0gcmVvcmRlcihDUk1fSU5URVJBQ1RJT05fVFlQRSxUb3RhbCkgLHkgPSBUb3RhbCkpICArCiAgICAgICAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsd2lkdGggPSAwLjUsIGZpbGw9J2JsYWNrJykgICsKICAgICAgICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gc2NhbGVzOjpjb21tYSkgKwogICAgICAgIGxhYnMoeCA9IkNSTSBJbnRlcmFjdGlvbiBUeXBlIiwgeSA9ICJDb3VudCIpICsgY29vcmRfZmxpcCgpICsKICAgICAgICB0aGVtZShsZWdlbmQudGV4dCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIpLAogICAgICAgICAgICAgIGxlZ2VuZC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIpLAogICAgICAgICAgICAgIGF4aXMudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDE0KSwKICAgICAgICAgICAgICBheGlzLnRleHQgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyKSkKICAgICAgCmdncGxvdGx5KGcpCmBgYAoKCiMjIyBDUk0gSW50ZXJhY3Rpb24gT3ZlciBUaW1lCmBgYHtyfQpjcm0gPC0gY3JtICU+JQogICAgICAgIG11dGF0ZShZZWFyID0gbHVicmlkYXRlOjp5ZWFyKENSTV9JTlRFUkFDVElPTl9EQVRFKSwKICAgICAgICAgICAgICAgUXVhcnRlciA9IGx1YnJpZGF0ZTo6cXVhcnRlcihDUk1fSU5URVJBQ1RJT05fREFURSksCiAgICAgICAgICAgICAgIE1vbnRoID0gbHVicmlkYXRlOjptb250aChDUk1fSU5URVJBQ1RJT05fREFURSwgbGFiZWwgPSBUUlVFKSwKICAgICAgICAgICAgICAgRE9XID0gbHVicmlkYXRlOjp3ZGF5KENSTV9JTlRFUkFDVElPTl9EQVRFLCBsYWJlbD1UUlVFKSkKYGBgCgojIyMgQ1JNIEludGVyYWN0aW9uIGJ5IFllYXIKYGBge3J9CmNybV95ZWFyIDwtIGNybSAlPiUKZ3JvdXBfYnkoWWVhciwgQ1JNX0lOVEVSQUNUSU9OX1RZUEUpICU+JQogICAgICAgIHN1bW1hcmlzZShUb3RhbCA9IG4oKSkgJT4lCiAgICAgICAgc2VsZWN0KFllYXIsQ1JNX0lOVEVSQUNUSU9OX1RZUEUsIFRvdGFsKQoKICAgZyA8LSBnZ3Bsb3QoY3JtX3llYXIsIGFlcyhhcy5mYWN0b3IoWWVhciksIFRvdGFsLCBncm91cD1DUk1fSU5URVJBQ1RJT05fVFlQRSwgY29sb3VyID0gQ1JNX0lOVEVSQUNUSU9OX1RZUEUpKSArIAogICAgICBnZW9tX2xpbmUoIGxpbmV3aWR0aD0xKSArIHRoZW1lX21pbmltYWwoKSArCiAgICAgIGxhYnMoeCA9ICJZZWFyIiwgeSA9ICJUb3RhbCIsIGNvbG9yPSJDUk0gSW50ZXJhY3Rpb24gVHlwZSIpICsgCiAgICAgIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBjb21tYSkgKwogICAgICB0aGVtZShsZWdlbmQudGV4dCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTApLAogICAgICAgICAgICBsZWdlbmQudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSwKICAgICAgICAgICAgYXhpcy50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTApLAogICAgICAgICAgICBheGlzLnRleHQgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSwKICAgICAgICAgICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSAwLCBoanVzdCA9IDEpKQpnZ3Bsb3RseShnKQpgYGAKCiMjIyBDUk0gSW50ZXJhY3Rpb24gYnkgUXVhcnRlcgpgYGB7cn0KY3JtICU+JQpncm91cF9ieShRdWFydGVyLCBDUk1fSU5URVJBQ1RJT05fVFlQRSkgJT4lCiAgICAgICAgc3VtbWFyaXNlKFRvdGFsID0gbigpKSAlPiUKICAgICAgICBzZWxlY3QoUXVhcnRlcixDUk1fSU5URVJBQ1RJT05fVFlQRSwgVG90YWwpICU+JSAKICAgICAgZ2dwbG90KGFlcyhhcy5mYWN0b3IoUXVhcnRlciksIFRvdGFsLCBncm91cD1DUk1fSU5URVJBQ1RJT05fVFlQRSwgY29sb3VyID0gQ1JNX0lOVEVSQUNUSU9OX1RZUEUpKSArIAogICAgICBnZW9tX2xpbmUoIGxpbmV3aWR0aD0xKSArIHRoZW1lX21pbmltYWwoKSArCiAgICAgIGxhYnMoeCA9ICJRdWFydGVyIiwgeSA9ICJUb3RhbCIsIGNvbG9yPSJDUk0gSW50ZXJhY3Rpb24gVHlwZSIpICsgCiAgICAgIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBjb21tYSkgKwogICAgICB0aGVtZShsZWdlbmQudGV4dCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTApLAogICAgICAgICAgICBsZWdlbmQudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSwKICAgICAgICAgICAgYXhpcy50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTApLAogICAgICAgICAgICBheGlzLnRleHQgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSwKICAgICAgICAgICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSAwLCBoanVzdCA9IDEpKQoKYGBgCgojIyMgQ1JNIEludGVyYWN0aW9uIGJ5IE1vbnRoCmBgYHtyfQpjcm0gJT4lCmdyb3VwX2J5KE1vbnRoLCBDUk1fSU5URVJBQ1RJT05fVFlQRSkgJT4lCiAgICAgICAgc3VtbWFyaXNlKFRvdGFsID0gbigpKSAlPiUKICAgICAgICBzZWxlY3QoTW9udGgsQ1JNX0lOVEVSQUNUSU9OX1RZUEUsIFRvdGFsKSAlPiUgCiAgICAgIGdncGxvdChhZXMoYXMuZmFjdG9yKE1vbnRoKSwgVG90YWwsIGdyb3VwPUNSTV9JTlRFUkFDVElPTl9UWVBFLCBjb2xvdXIgPSBDUk1fSU5URVJBQ1RJT05fVFlQRSkpICsgCiAgICAgIGdlb21fbGluZSggbGluZXdpZHRoPTEpICsgdGhlbWVfbWluaW1hbCgpICsKICAgICAgbGFicyh4ID0gIk1vbnRoIiwgeSA9ICJUb3RhbCIsIGNvbG9yPSJDUk0gSW50ZXJhY3Rpb24gVHlwZSIpICsgCiAgICAgIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBjb21tYSkgKwogICAgICB0aGVtZShsZWdlbmQudGV4dCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTApLAogICAgICAgICAgICBsZWdlbmQudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSwKICAgICAgICAgICAgYXhpcy50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTApLAogICAgICAgICAgICBheGlzLnRleHQgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSwKICAgICAgICAgICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSAwLCBoanVzdCA9IDEpKQpgYGAKCgojIyMgQ1JNIEludGVyYWN0aW9uIGJ5IERheSBvZiBXZWVrCmBgYHtyfQpjcm0gJT4lCmdyb3VwX2J5KERPVywgQ1JNX0lOVEVSQUNUSU9OX1RZUEUpICU+JQogICAgICAgIHN1bW1hcmlzZShUb3RhbCA9IG4oKSkgJT4lCiAgICAgICAgc2VsZWN0KERPVyxDUk1fSU5URVJBQ1RJT05fVFlQRSwgVG90YWwpICU+JSAKICAgICAgZ2dwbG90KGFlcyhhcy5mYWN0b3IoRE9XKSwgVG90YWwsIGdyb3VwPUNSTV9JTlRFUkFDVElPTl9UWVBFLCBjb2xvdXIgPSBDUk1fSU5URVJBQ1RJT05fVFlQRSkpICsgCiAgICAgIGdlb21fbGluZSggbGluZXdpZHRoPTEpICsgdGhlbWVfbWluaW1hbCgpICsKICAgICAgbGFicyh4ID0gIkRheSBvZiBXZWVrIiwgeSA9ICJUb3RhbCIsIGNvbG9yPSJDUk0gSW50ZXJhY3Rpb24gVHlwZSIpICsgCiAgICAgIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBjb21tYSkgKwogICAgICB0aGVtZShsZWdlbmQudGV4dCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTApLAogICAgICAgICAgICBsZWdlbmQudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSwKICAgICAgICAgICAgYXhpcy50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTApLAogICAgICAgICAgICBheGlzLnRleHQgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSwKICAgICAgICAgICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSAwLCBoanVzdCA9IDEpKQpgYGAKIyMgR2lmdCBPdmVydmlldwoKIyMjIEdpZnRzIGJ5IENSTSBJbnRlcmFjdGlvbiBUeXBlCmBgYHtyfQpsZWZ0X2pvaW4oZ2lmdCxjcm0sYnk9J0NPTlNUSVRVRU5UX0lEJykgJT4lCiAgZ3JvdXBfYnkoQ1JNX0lOVEVSQUNUSU9OX1RZUEUpICU+JQogIHN1bW1hcmlzZShUb3RhbCA9IHN1bShBTU9VTlQpKSAlPiUKICBzZWxlY3QoQ1JNX0lOVEVSQUNUSU9OX1RZUEUsVG90YWwpICU+JQogIGdncGxvdChhZXMoeCA9IHJlb3JkZXIoQ1JNX0lOVEVSQUNUSU9OX1RZUEUsVG90YWwpICx5ID0gVG90YWwpKSAgKwogICAgICAgIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiLHdpZHRoID0gMC41LCBmaWxsPSdibGFjaycpICArCiAgICAgICAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IHNjYWxlczo6Y29tbWEpICsKICAgICAgICBsYWJzKHggPSJDUk0gSW50ZXJhY3Rpb24gVHlwZSIsIHkgPSAiRG9uYXRpb25zIikgKyBjb29yZF9mbGlwKCkgKwogICAgICAgIHRoZW1lKGxlZ2VuZC50ZXh0ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiksCiAgICAgICAgICAgICAgbGVnZW5kLnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiksCiAgICAgICAgICAgICAgYXhpcy50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTQpLAogICAgICAgICAgICAgIGF4aXMudGV4dCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIpKQoKCmBgYAoKIyMjIEdpZnRzIG92ZXJ0aW1lCmBgYHtyfQpnaWZ0IDwtIGdpZnQgJT4lCiAgICAgICAgbXV0YXRlKFllYXIgPSBsdWJyaWRhdGU6OnllYXIoR0lGVF9EQVRFKSwKICAgICAgICAgICAgICAgUXVhcnRlciA9IGx1YnJpZGF0ZTo6cXVhcnRlcihHSUZUX0RBVEUpLAogICAgICAgICAgICAgICBNb250aCA9IGx1YnJpZGF0ZTo6bW9udGgoR0lGVF9EQVRFLCBsYWJlbCA9IFRSVUUpLAogICAgICAgICAgICAgICBET1cgPSBsdWJyaWRhdGU6OndkYXkoR0lGVF9EQVRFLCBsYWJlbD1UUlVFKSkKYGBgCgojIyMgR2lmdCBieSBZZWFyCmBgYHtyfQpnIDwtIGdpZnQgJT4lCmdyb3VwX2J5KFllYXIpICU+JQogICAgICAgIHN1bW1hcmlzZShUb3RhbCA9IHN1bShBTU9VTlQpKSAlPiUKICAgICAgICBzZWxlY3QoWWVhciwgVG90YWwpICU+JSAKICAgICAgICBuYS5vbWl0KCkgJT4lCiAgICAgIGdncGxvdChhZXMoWWVhciwgVG90YWwpKSArIAogICAgICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5Iix3aWR0aCA9IDAuNSwgZmlsbD0nYmxhY2snKSArIHRoZW1lX21pbmltYWwoKSArCiAgICAgIGxhYnMoeCA9ICJZZWFyIiwgeSA9ICJUb3RhbCIpICsgCiAgICAgIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBjb21tYSkgKwogICAgICB0aGVtZShsZWdlbmQudGV4dCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTApLAogICAgICAgICAgICBsZWdlbmQudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSwKICAgICAgICAgICAgYXhpcy50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTApLAogICAgICAgICAgICBheGlzLnRleHQgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSwKICAgICAgICAgICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCwgaGp1c3QgPSAxKSkKZ2dwbG90bHkoZykKYGBgCgojIyMgR2lmdCBieSBRdWFydGVyCmBgYHtyfQpnIDwtIGdpZnQgJT4lCmdyb3VwX2J5KFF1YXJ0ZXIpICU+JQogICAgICAgIHN1bW1hcmlzZShUb3RhbCA9IHN1bShBTU9VTlQpKSAlPiUKICAgICAgICBzZWxlY3QoUXVhcnRlciwgVG90YWwpICU+JSAKICAgICAgICBuYS5vbWl0KCkgJT4lCiAgICAgIGdncGxvdChhZXMoUXVhcnRlciwgVG90YWwpKSArIAogICAgICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5Iix3aWR0aCA9IDAuNSwgZmlsbD0nYmxhY2snKSArIHRoZW1lX21pbmltYWwoKSArCiAgICAgIGxhYnMoeCA9ICJRdWFydGVyIiwgeSA9ICJUb3RhbCIpICsgCiAgICAgIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBjb21tYSkgKwogICAgICB0aGVtZShsZWdlbmQudGV4dCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTApLAogICAgICAgICAgICBsZWdlbmQudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSwKICAgICAgICAgICAgYXhpcy50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTApLAogICAgICAgICAgICBheGlzLnRleHQgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSwKICAgICAgICAgICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSAwLCBoanVzdCA9IDEpKQpnZ3Bsb3RseShnKQpgYGAKCiMjIyBHaWZ0IGJ5IE1vbnRoCmBgYHtyfQpnIDwtIGdpZnQgJT4lCmdyb3VwX2J5KE1vbnRoKSAlPiUKICAgICAgICBzdW1tYXJpc2UoVG90YWwgPSBzdW0oQU1PVU5UKSkgJT4lCiAgICAgICAgc2VsZWN0KE1vbnRoLCBUb3RhbCkgJT4lIAogICAgICAgIG5hLm9taXQoKSAlPiUKICAgICAgZ2dwbG90KGFlcyhNb250aCwgVG90YWwpKSArIAogICAgICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5Iix3aWR0aCA9IDAuNSwgZmlsbD0nYmxhY2snKSArIHRoZW1lX21pbmltYWwoKSArCiAgICAgIGxhYnMoeCA9ICJNb250aCIsIHkgPSAiVG90YWwiKSArIAogICAgICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gY29tbWEpICsKICAgICAgdGhlbWUobGVnZW5kLnRleHQgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSwKICAgICAgICAgICAgbGVnZW5kLnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCksCiAgICAgICAgICAgIGF4aXMudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSwKICAgICAgICAgICAgYXhpcy50ZXh0ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCksCiAgICAgICAgICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gMCwgaGp1c3QgPSAxKSkKZ2dwbG90bHkoZykKYGBgCgpHaWZ0IGJ5IERheSBvZiBXZWVrCmBgYHtyfQpnIDwtIGdpZnQgJT4lCmdyb3VwX2J5KERPVykgJT4lCiAgICAgICAgc3VtbWFyaXNlKFRvdGFsID0gc3VtKEFNT1VOVCkpICU+JQogICAgICAgIHNlbGVjdChET1csIFRvdGFsKSAlPiUgCiAgICAgICAgbmEub21pdCgpICU+JQogICAgICBnZ3Bsb3QoYWVzKERPVywgVG90YWwpKSArIAogICAgICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5Iix3aWR0aCA9IDAuNSwgZmlsbD0nYmxhY2snKSArIHRoZW1lX21pbmltYWwoKSArCiAgICAgIGxhYnMoeCA9ICJEYXkgb2YgV2VlayIsIHkgPSAiVG90YWwiKSArIAogICAgICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gY29tbWEpICsKICAgICAgdGhlbWUobGVnZW5kLnRleHQgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSwKICAgICAgICAgICAgbGVnZW5kLnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCksCiAgICAgICAgICAgIGF4aXMudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSwKICAgICAgICAgICAgYXhpcy50ZXh0ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCksCiAgICAgICAgICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gMCwgaGp1c3QgPSAxKSkKZ2dwbG90bHkoZykKYGBgCgojIyBWaWRlbyBPdmVydmlldwojIyMgVmlkZW8gVmlld3Mgb3ZlciB0aW1lCmBgYHtyfQp2aWRlbyA8LSB2aWRlbyAlPiUKICAgICAgICBtdXRhdGUoWWVhciA9IGx1YnJpZGF0ZTo6eWVhcihTRU5UX0RBVEUpLAogICAgICAgICAgICAgICBRdWFydGVyID0gbHVicmlkYXRlOjpxdWFydGVyKFNFTlRfREFURSksCiAgICAgICAgICAgICAgIE1vbnRoID0gbHVicmlkYXRlOjptb250aChTRU5UX0RBVEUsIGxhYmVsID0gVFJVRSksCiAgICAgICAgICAgICAgIERPVyA9IGx1YnJpZGF0ZTo6d2RheShTRU5UX0RBVEUsIGxhYmVsPVRSVUUpKQpgYGAKCiMjIyBWaWRlbyB2aWV3cyBieSB5ZWFyCmBgYHtyfQpnIDwtIHZpZGVvICU+JQpncm91cF9ieShZZWFyKSAlPiUKICAgICAgICBzdW1tYXJpc2UoVG90YWwgPSBzdW0oVklERU9fVklFV1MpKSAlPiUKICAgICAgICBzZWxlY3QoWWVhciwgVG90YWwpICU+JSAKICAgICAgICBuYS5vbWl0KCkgJT4lCiAgICAgIGdncGxvdChhZXMoYXMuZmFjdG9yKFllYXIpLCBUb3RhbCkpICsgCiAgICAgIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiLHdpZHRoID0gMC41LCBmaWxsPSdibGFjaycpICsgdGhlbWVfbWluaW1hbCgpICsKICAgICAgbGFicyh4ID0gIlllYXIiLCB5ID0gIlRvdGFsIikgKyAKICAgICAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IGNvbW1hKSArCiAgICAgIHRoZW1lKGxlZ2VuZC50ZXh0ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCksCiAgICAgICAgICAgIGxlZ2VuZC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTApLAogICAgICAgICAgICBheGlzLnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCksCiAgICAgICAgICAgIGF4aXMudGV4dCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTApLAogICAgICAgICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDAsIGhqdXN0ID0gMSkpCmdncGxvdGx5KGcpCmBgYAoKIyMjIFZpZGVvIFZpZXdzIGJ5IFF1YXJ0ZXIKYGBge3J9CmcgPC0gdmlkZW8gJT4lCmdyb3VwX2J5KFF1YXJ0ZXIpICU+JQogICAgICAgIHN1bW1hcmlzZShUb3RhbCA9IHN1bShWSURFT19WSUVXUykpICU+JQogICAgICAgIHNlbGVjdChRdWFydGVyLCBUb3RhbCkgJT4lIAogICAgICAgIG5hLm9taXQoKSAlPiUKICAgICAgZ2dwbG90KGFlcyhRdWFydGVyLCBUb3RhbCkpICsgCiAgICAgIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiLHdpZHRoID0gMC41LCBmaWxsPSdibGFjaycpICsgdGhlbWVfbWluaW1hbCgpICsKICAgICAgbGFicyh4ID0gIlF1YXJ0ZXIiLCB5ID0gIlRvdGFsIikgKyAKICAgICAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IGNvbW1hKSArCiAgICAgIHRoZW1lKGxlZ2VuZC50ZXh0ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCksCiAgICAgICAgICAgIGxlZ2VuZC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTApLAogICAgICAgICAgICBheGlzLnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCksCiAgICAgICAgICAgIGF4aXMudGV4dCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTApLAogICAgICAgICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDAsIGhqdXN0ID0gMSkpCmdncGxvdGx5KGcpCmBgYAoKIyMjIFZpZGVvIFZpZXdzIGJ5IE1vbnRoCmBgYHtyfQpnIDwtIHZpZGVvICU+JQpncm91cF9ieShNb250aCkgJT4lCiAgICAgICAgc3VtbWFyaXNlKFRvdGFsID0gc3VtKFZJREVPX1ZJRVdTKSkgJT4lCiAgICAgICAgc2VsZWN0KE1vbnRoLCBUb3RhbCkgJT4lIAogICAgICAgIG5hLm9taXQoKSAlPiUKICAgICAgZ2dwbG90KGFlcyhNb250aCwgVG90YWwpKSArIAogICAgICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5Iix3aWR0aCA9IDAuNSwgZmlsbD0nYmxhY2snKSArIHRoZW1lX21pbmltYWwoKSArCiAgICAgIGxhYnMoeCA9ICJNb250aCIsIHkgPSAiVG90YWwiKSArIAogICAgICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gY29tbWEpICsKICAgICAgdGhlbWUobGVnZW5kLnRleHQgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSwKICAgICAgICAgICAgbGVnZW5kLnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCksCiAgICAgICAgICAgIGF4aXMudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSwKICAgICAgICAgICAgYXhpcy50ZXh0ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCksCiAgICAgICAgICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gMCwgaGp1c3QgPSAxKSkKZ2dwbG90bHkoZykKYGBgCgojIyMgVmlkZW8gVmlld3MgYnkgRGF5IG9mIFdlZWsKYGBge3J9CmcgPC0gdmlkZW8gJT4lCmdyb3VwX2J5KERPVykgJT4lCiAgICAgICAgc3VtbWFyaXNlKFRvdGFsID0gc3VtKFZJREVPX1ZJRVdTKSkgJT4lCiAgICAgICAgc2VsZWN0KERPVywgVG90YWwpICU+JSAKICAgICAgICBuYS5vbWl0KCkgJT4lCiAgICAgIGdncGxvdChhZXMoRE9XLCBUb3RhbCkpICsgCiAgICAgIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiLHdpZHRoID0gMC41LCBmaWxsPSdibGFjaycpICsgdGhlbWVfbWluaW1hbCgpICsKICAgICAgbGFicyh4ID0gIkRheSBvZiB0aGUgd2VlayIsIHkgPSAiVG90YWwiKSArIAogICAgICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gY29tbWEpICsKICAgICAgdGhlbWUobGVnZW5kLnRleHQgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSwKICAgICAgICAgICAgbGVnZW5kLnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCksCiAgICAgICAgICAgIGF4aXMudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSwKICAgICAgICAgICAgYXhpcy50ZXh0ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCksCiAgICAgICAgICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gMCwgaGp1c3QgPSAxKSkKZ2dwbG90bHkoZykKYGBgCgojIFBhcnQgMjogQSBUYWxlIG9mIFBvcnRmb2xpb3MvQ3VzdG9tZXIgU2VnbWVudGF0aW9uCiMjIERvbm9yIFJGTSBBbmFseXNpcwpgYGB7cn0KCnJmbV9kZiA8LSBnaWZ0ICU+JQogIHNlbGVjdChDT05TVElUVUVOVF9JRCxHSUZUX0RBVEUsQU1PVU5UKSAlPiUKICBuYS5vbWl0KCkKCm5hbWVzKHJmbV9kZilbbmFtZXMocmZtX2RmKSA9PSAnQ09OU1RJVFVFTlRfSUQnXSA8LSAnY3VzdG9tZXJfaWQnCgojcmZtIG1vZGVsIHNldHVwCmFuYWx5c2lzX2RhdGUgPC0gbHVicmlkYXRlOjphc19kYXRlKHRvZGF5KCksIHR6ID0gIlVUQyIpCgpyZXBvcnQgPC0gcmZtX3RhYmxlX29yZGVyKHJmbV9kZiwgY3VzdG9tZXJfaWQsR0lGVF9EQVRFLEFNT1VOVCwgYW5hbHlzaXNfZGF0ZSkKI3NlZ21lbnQKc2VnbWVudF90aXRsZXMgPC0gYygiRmlyc3QgR3JhZGUiLCAiTG95YWwiLCAiTGlrZWx5IHRvIGJlIExveWFsIiwKICAgICAgICAgICAgICAgICAgICAiTmV3IE9uZXMiLCAiQ291bGQgYmUgUHJvbWlzaW5nIiwgIlJlcXVpcmUgQXNzaXN0YW5jZSIsICJHZXR0aW5nIExlc3MgRnJlcXVlbnQiLAogICAgICAgICAgICAgICAgICAgICJBbG1vc3QgT3V0IiwgIkNhbid0IExvc2UgVGhlbSIsICJEb27igJl0IFNob3cgVXAgYXQgQWxsIikKI251bWVyaWNhbCB0aHJlc2hvbGRzCiByX2xvdyA8LSBjKDQsIDIsIDMsIDQsIDMsIDIsIDIsIDEsIDEsIDEpCiByX2hpZ2ggPC0gYyg1LCA1LCA1LCA1LCA0LCAzLCAzLCAyLCAxLCAyKQogZl9sb3cgPC0gYyg0LCAzLCAxLCAxLCAxLCAyLCAxLCAyLCA0LCAxKQogZl9oaWdoIDwtIGMoNSwgNSwgMywgMSwgMSwgMywgMiwgNSwgNSwgMikKIG1fbG93IDwtIGMoNCwgMywgMSwgMSwgMSwgMiwgMSwgMiwgNCwgMSkKIG1faGlnaCAgPC0gYyg1LCA1LCAzLCAxLCAxLCAzLCAyLCA1LCA1LCAyKQoKZGl2aXNpb25zPC1yZm1fc2VnbWVudChyZXBvcnQsIHNlZ21lbnRfdGl0bGVzLCByX2xvdywgcl9oaWdoLCBmX2xvdywgZl9oaWdoLCBtX2xvdywgbV9oaWdoKQoKI25hbWVzKGRpdmlzaW9ucylbbmFtZXMoZGl2aXNpb25zKSA9PSAnY3VzdG9tZXJfaWQnXSA8LSAnQ09OU1RJVFVFTlRfSUQnCgpkaXZpc2lvbl9jb3VudCA8LSBkaXZpc2lvbnMgJT4lIGNvdW50KHNlZ21lbnQpICU+JSBhcnJhbmdlKGRlc2MobikpICU+JSByZW5hbWUoU2VnbWVudCA9IHNlZ21lbnQsIENvdW50ID0gbikKYGBgCgojIyMgUkZNIGhlYXRtYXAKYGBge3J9CnJmbV9wbG90X2hlYXRtYXAocmVwb3J0KQpgYGAKCmBgYHtyfQpyZm1fcGxvdF9iYXJfY2hhcnQocmVwb3J0KQpgYGAKYGBge3J9CnJmbV9wbG90X2hpc3RvZ3JhbShyZXBvcnQpCmBgYAoKYGBge3J9CgpgYGAKCiMjIERvbm9yIExpZmV0aW1lIFZhbHVlCmBgYHtyfQojIENhbGN1bGF0ZSBhdmVyYWdlIGRvbmF0aW9uIHBlciBjdXN0b21lcgphdmdfZG9uYXRpb25fcGVyX2N1c3RvbWVyIDwtIGdpZnQgJT4lCiAgZ3JvdXBfYnkoQ09OU1RJVFVFTlRfSUQpICU+JQogIHN1bW1hcml6ZShhdmdfcmV2ZW51ZSA9IG1lYW4oQU1PVU5UKSkKCiMgQ2FsY3VsYXRlIGF2ZXJhZ2UgZG9ub3IgbGlmZXNwYW4gKHNpbXBsaWZpZWQsIHVzaW5nIHRoZSBudW1iZXIgb2YgbW9udGhzKQphdmdfZG9ub3JfbGlmZXNwYW4gPC0gZ2lmdCAlPiUKICBncm91cF9ieShDT05TVElUVUVOVF9JRCkgJT4lIAogIHN1bW1hcml6ZShhdmdfbGlmZXNwYW4gPSBhcy5udW1lcmljKGRpZmZ0aW1lKG1heChHSUZUX0RBVEUpLCBtaW4oR0lGVF9EQVRFKSwgdW5pdHMgPSAiZGF5cyIpKSkgJT4lCiAgbmEub21pdCgpCgojIENhbGN1bGF0ZSBDTFYKY2x2X2RmIDwtIGlubmVyX2pvaW4oYXZnX2RvbmF0aW9uX3Blcl9jdXN0b21lcixhdmdfZG9ub3JfbGlmZXNwYW4sYnk9J0NPTlNUSVRVRU5UX0lEJykgJT4lCmdyb3VwX2J5KENPTlNUSVRVRU5UX0lEKSAlPiUKICBtdXRhdGUoQ0xWX2NhbGMgPSBhdmdfcmV2ZW51ZSAqIGF2Z19saWZlc3BhbikgJT4lCiAgc2VsZWN0KENPTlNUSVRVRU5UX0lELENMVl9jYWxjKQoKCmBgYAoKCgoKCmBgYHtyfQojIyBDTFYgdmVyc2lvbiAyCiMgMi4gQ2FsY3VsYXRlIGtleSBtZXRyaWNzIGZvciBlYWNoIGN1c3RvbWVyCmN1c3RvbWVyX2RhdGEgPC0gdHJhbnNhY3Rpb25zICU+JQogIGdyb3VwX2J5KGN1c3RvbWVyX2lkKSAlPiUKICBzdW1tYXJpc2UoCiAgICBmcmVxdWVuY3kgPSBuKCkgLSAxLCAjIE51bWJlciBvZiByZXBlYXQgcHVyY2hhc2VzCiAgICByZWNlbmN5ID0gYXMubnVtZXJpYyhkaWZmdGltZShtYXgoZGF0ZSksIG1pbihkYXRlKSwgdW5pdHMgPSAiZGF5cyIpKSwKICAgIHRvdGFsX3JldmVudWUgPSBzdW0ocmV2ZW51ZSksCiAgICBtb25ldGFyeSA9IHRvdGFsX3JldmVudWUgLyBuKCkKICApCgojIDMuIENhbGN1bGF0ZSBjb21wb25lbnRzIGZvciBoaXN0b3JpY2FsIENMVgojIEF2ZXJhZ2UgUHVyY2hhc2UgVmFsdWUgKEFQVikKYXB2IDwtIHN1bSh0cmFuc2FjdGlvbnMkcmV2ZW51ZSkgLyBucm93KHRyYW5zYWN0aW9ucykKCiMgQXZlcmFnZSBQdXJjaGFzZSBGcmVxdWVuY3kgUmF0ZSAoQVBGUikKdG90YWxfcHVyY2hhc2VzIDwtIG5yb3codHJhbnNhY3Rpb25zKQp0b3RhbF9jdXN0b21lcnMgPC0gbGVuZ3RoKHVuaXF1ZSh0cmFuc2FjdGlvbnMkY3VzdG9tZXJfaWQpKQphcGZyIDwtIHRvdGFsX3B1cmNoYXNlcyAvIHRvdGFsX2N1c3RvbWVycwoKIyBDdXN0b21lciBWYWx1ZSAoQ1YpCmN2IDwtIGFwdiAqIGFwZnIKCiMgNC4gQ2FsY3VsYXRlIGEgc2ltcGxlIGhpc3RvcmljYWwgQ0xWCiMgV2UgbmVlZCB0byBhc3N1bWUgYSBjdXN0b21lciBsaWZldGltZS4gTGV0J3MgYXNzdW1lIGFuIGF2ZXJhZ2UgY3VzdG9tZXIgbGlmZXRpbWUgb2YgMzY1IGRheXMuCiMgQW5kIGEgcHJvZml0IG1hcmdpbiBvZiAyMCUKcHJvZml0X21hcmdpbiA8LSAwLjIwCmF2ZXJhZ2VfY3VzdG9tZXJfbGlmZXRpbWUgPC0gMzY1ICMgaW4gZGF5cwoKIyBBdmVyYWdlIGN1c3RvbWVyIGxpZmVzcGFuIGluIHJlbGF0aW9uIHRvIHRoZSBvYnNlcnZhdGlvbiBwZXJpb2QKIyBIZXJlIHdlIGNhbGN1bGF0ZSBhdmVyYWdlIGxpZmVzcGFuIGJhc2VkIG9uIHJlY2VuY3kKYXZlcmFnZV9saWZlc3BhbiA8LSBtZWFuKGN1c3RvbWVyX2RhdGEkcmVjZW5jeSkKCiMgU2ltcGxlIENMVgpjbHZfc2ltcGxlIDwtIGN2ICogYXZlcmFnZV9saWZlc3BhbiAqIHByb2ZpdF9tYXJnaW4KYGBgCgoKYGBge3J9CgpgYGAKCgpgYGB7cn0KCmBgYAoKCmBgYHtyfQoKYGBgCgojIFBhcnQgMzogVGhlIFBhdGggRm9yd2FyZDogQWN0aXZhdGluZyBPdXIgU3RyYXRlZ3kKCmBgYHtyfQoKYGBgCgoKYGBge3J9CgpgYGAKCgpgYGB7cn0KCmBgYAoKCmBgYHtyfQoKYGBgCg==